# -*- Ruby -*-
grammar MethodName
  rule upcase_letter
    [A-Z]
  end 
  rule downcase_letter
    [a-z]
  end
  rule suffix_letter
    [=!?]
  end    
  rule letter
    upcase_letter | downcase_letter
  end
  rule id_symbol
    letter | '_' | [0-9]
  end

  # Examples: 
  # var1
  # my_var? 
  rule variable_identifier
    ((downcase_letter | '_') id_symbol* suffix_letter?)
    { 
      SymbolEntry = Struct.new(:type, :name, :chain)
      def value
         SymbolEntry.new(:variable, self.to_s)
      end   
    }
  end 

  # Examples: 
  # MY_CONSTANT
  # MyConstant_01
  rule constant_identifier
    (upcase_letter id_symbol*)
    { 
      def value
         SymbolEntry.new(:constant, self.to_s)
      end   
    }
  end 

  # Examples:
  # $global_variable
  # We won't try for funny global names like $$, $? $:, $', etc
  rule global_identifier
    ('$' (constant_identifier | variable_identifier))
    {
      def value
         SymbolEntry.new(:global, self.to_s)
      end   
    }     
  end 

  # Examples: 
  #   Foo
  #   foo
  rule local_identifier
    (constant_identifier | variable_identifier)
    {
      def value
         SymbolEntry.new(:instance, self.to_s)
      end   
    }     
  end 

  # Example: @foo
  rule instance_identifier
    ('@' local_identifier )
    {
      def value
         SymbolEntry.new(:instance, self.to_s)
      end   
    }     
  end 


  # Example: @@foo
  rule classvar_identifier
    ('@@' local_identifier )
    {
      def value
         SymbolEntry.new(:classvar, self.to_s)
      end   
    }     
  end 
  rule identifier
    global_identifier | instance_identifier | classvar_identifier |
    local_identifier 
  end 

  # I think strict Ruby rules are that once one goes from :: to .
  # There is no going back. That is, A.B::C is invalid. 
  # 
  # Also I think method names can't be constants. But such 
  # subtleties we'll handle in semantic analysis.
  rule class_module_chain
    ( ( parent:(local_identifier) symbol:('::'|'.')
        child:(class_module_chain) )
     | (parent:(local_identifier) ) )
    {
      def value
        let = self.to_s[0..0]
        type = (let.capitalize == let) ? :constant : :variable
        SymbolEntry.new(type, self.to_s, [parent, child, symbol.to_s])
      end   
    }
  end

  ##############################################################
  # Location-specific things. This is used in conjunction with
  # method-like things above.
  rule sp
    [ \t]+
  end 

  rule file_pos_sep
    sp | ':'
  end 


  rule integer
    [0-9]+ { to_i }
  end 

  rule vm_offset 
    ('@' integer) 
  {
   Position = Struct.new(:type, :value) unless defined?(Position)
   Position.new(:offset, integer.value)
  }
  end

  rule line_number 
    ([0-9]+)
  {
    Position = Struct.new(:type, :value) unless defined?(Position)
    Position.new(:line, to_i)
  }
  end  

  # Examples:
  #  @43
  #  5
  rule position
    (vm_offset | line_number)
  end  

  rule location
   (class_module_chain? file_pos_sep position
     | position | meth2:(class_module_chain))
   {
    Location = Struct.new(:method_name, :position) unless defined?(Location)
    pos_val = position ? position.value : nil
    meth_val = 
        if class_module_chain
          class_module_chain.value
        else        
          # p ['++++2', meth2, meth2.class]
          meth2 ? meth2.value : nil
        end
    Location.new(meth_val, pos_val)
   }
  end

end
